use std::{collections::HashMap, path::PathBuf, process::exit};

use ::mysql::{Opts, Pool};
use ::oracle::pool::{self, PoolBuilder};
use clap::{Parser, Subcommand, ValueHint};
use serde::Deserialize;

mod mysql;
mod oracle;

#[derive(Debug, Default, Clone, Deserialize)]
pub struct UserPrelude {
    pub user: Option<String>,
    pub oracle: OracleUserPrelude,
    pub mysql: MySQLUserPrelude,
}

#[derive(Debug, Default, Clone, Deserialize)]
pub struct OracleUserPrelude {
    pub role_privs: Vec<String>,
    pub sys_privs: Vec<String>,
    pub tab_privs: HashMap<String, Vec<String>>,
    pub dept_views: HashMap<String, Vec<String>>,
}

#[derive(Debug, Default, Clone, Deserialize)]
pub struct MySQLUserPrelude {
    pub role_privs: Vec<String>,
    pub sys_privs: Vec<String>,
    pub tab_privs: HashMap<String, Vec<String>>,
    pub permissible_static_privileges: HashMap<String, String>,
}

// https://docs.rs/clap/latest/clap/_derive/index.html
#[derive(Parser, Clone, Debug)]
#[command(about, long_about = None)]
pub struct Cli {
    #[command(subcommand)]
    command: Commands,
}

#[derive(Subcommand, Clone, Debug)]
pub enum Commands {
    Oracle {
        /// User for login if not current user.
        #[arg(short, long, value_hint = ValueHint::Username)]
        user: String,

        /// Connect to host.
        #[arg(short = 'H', long, value_hint = ValueHint::Hostname)]
        host: String,

        /// Password to use when connecting to server
        #[arg(short, long)]
        password: String,

        /// Port number to use for connection
        #[arg(short = 'P', long, default_value = "1521")]
        port: Option<usize>,

        /// Page size for Query Database
        #[arg(short = 's', long, default_value = "500")]
        page_size: Option<usize>,

        /// Show nullable unique index in column UK
        #[arg(long, default_value = "false")]
        show_nullable_uk: Option<bool>,

        #[arg(long)]
        service_name: String,

        #[arg(long, value_hint = ValueHint::FilePath)]
        build_in_db: PathBuf,

        #[arg(long = "datatype-support", action = clap::ArgAction::Append)]
        data_type_support: Option<Vec<String>>,

        #[arg(long = "datatype-unsupport", action = clap::ArgAction::Append)]
        data_type_unsupport: Option<Vec<String>>,

        #[arg(long = "datatype-support-limited", action = clap::ArgAction::Append)]
        data_type_support_limited: Option<Vec<String>>,
        /// User manifest file: xlsx.
        #[arg(long, value_hint = ValueHint::FilePath)]
        manifest_file: PathBuf,
        /// User grant manifest file: toml.
        #[arg(long, value_hint = ValueHint::FilePath)]
        grant_manifest_file: Option<PathBuf>,
        /// User Report File: xlsx.
        #[arg(long, value_hint = ValueHint::FilePath)]
        report_file: Option<PathBuf>,
    },

    Mysql {
        /// User for login if not current user.
        #[arg(short, long, value_hint = ValueHint::Username)]
        user: String,

        /// Connect to host.
        #[arg(short = 'H', long, value_hint = ValueHint::Hostname)]
        host: String,

        /// Password to use when connecting to server
        #[arg(short, long)]
        password: String,

        /// Port number to use for connection
        #[arg(short = 'P', long, default_value = "3306")]
        port: Option<usize>,

        /// Page size for Query Database
        #[arg(short = 's', long, default_value = "500")]
        page_size: Option<usize>,

        /// Show nullable unique index in column UK
        #[arg(long, default_value = "false")]
        show_nullable_uk: Option<bool>,

        #[arg(long)]
        db_name: Option<String>,

        #[arg(long, value_hint = ValueHint::FilePath)]
        build_in_db: PathBuf,

        #[arg(long = "datatype-support", action = clap::ArgAction::Append)]
        data_type_support: Option<Vec<String>>,

        #[arg(long = "datatype-unsupport", action = clap::ArgAction::Append)]
        data_type_unsupport: Option<Vec<String>>,

        #[arg(long = "datatype-support-limited", action = clap::ArgAction::Append)]
        data_type_support_limited: Option<Vec<String>>,
        /// User manifest file: xlsx.
        #[arg(long, value_hint = ValueHint::FilePath)]
        manifest_file: PathBuf,
        /// User grant manifest file: toml.
        #[arg(long, value_hint = ValueHint::FilePath)]
        grant_manifest_file: Option<PathBuf>,
        /// User Report File: xlsx.
        #[arg(long, value_hint = ValueHint::FilePath)]
        report_file: Option<PathBuf>,
    },

    /// Output version information and exit.
    Version {},
}


#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let args = Cli::parse();
    match args.command {
        Commands::Oracle {
            user,
            host,
            password,
            port,
            service_name,
            build_in_db,
            manifest_file,
            grant_manifest_file,
            page_size,
            show_nullable_uk,
            data_type_support,
            data_type_unsupport,
            data_type_support_limited,
            report_file,
        } => {
            // let manifest_file = PathBuf::from(manifest_file);
            // let report_file = match report_file {
            //     Some(report_file) => Some(PathBuf::from(report_file)),
            //     None => None
            // };
            let port = match port {
                Some(port) => port,
                None => 1521,
            };

            let page_size = match page_size {
                Some(page_size) => page_size,
                None => oracle::table::PAGE_SIZE,
            };

            log::debug!("Build-in Data Type: {:?}", oracle::table::TYPE_SUPPORT);

            // Default
            let mut new_data_type_support = HashMap::new();
            for typ in oracle::table::TYPE_SUPPORT {
                new_data_type_support.insert(typ.to_string(), 1);
            }

            // 完全支持
            if let Some(data_type_support) = data_type_support {
                for typ in data_type_support {
                    if !new_data_type_support.contains_key(typ.as_str()) {
                        new_data_type_support.insert(typ.to_string(), 1);
                    }
                }
            }
            // 不支持
            if let Some(data_type_unsupport) = data_type_unsupport {
                for typ in data_type_unsupport {
                    if new_data_type_support.contains_key(typ.as_str()) {
                        new_data_type_support.remove(typ.as_str());
                    }
                }
            }
            // 有限支持
            if let Some(data_type_support_limited) = data_type_support_limited.clone() {
                for typ in data_type_support_limited {
                    if new_data_type_support.contains_key(typ.as_str()) {
                        new_data_type_support.remove(typ.as_str());
                    }
                }
            }

            let new_data_type_support: Vec<String> = new_data_type_support.into_keys().collect();

            let pool = PoolBuilder::new(
                user.clone(),
                password,
                format!("//{}:{}/{}", host, port, service_name),
            )
            .max_connections(10)
            .build();
            let mut pool = match pool {
                Ok(pool) => pool,
                Err(e) => {
                    println!("{e}");
                    exit(1);
                }
            };
            pool.set_get_mode(&pool::GetMode::Wait).unwrap();
            log::debug!("pool: {:?}", pool);
            // let ora_conn = Connection::connect(user, password, format!("//{}:{}/{}", host, port, service_name)).unwrap();
            let sqlite_conn = sqlite::open(build_in_db).unwrap();
            oracle::probe(
                &sqlite_conn,
                &pool,
                &manifest_file,
                &new_data_type_support,
                &data_type_support_limited,
                page_size,
                &report_file,
                &user,
                &grant_manifest_file,
                show_nullable_uk.unwrap()
            )
            .await;
        }
        Commands::Mysql {
            user,
            host,
            password,
            port,
            db_name,
            build_in_db,
            manifest_file,
            grant_manifest_file,
            page_size,
            show_nullable_uk,
            data_type_support,
            data_type_unsupport,
            data_type_support_limited,
            report_file,
        } => {
            // let manifest_file = PathBuf::from(manifest_file);
            // let report_file = match report_file {
            //     Some(report_file) => Some(PathBuf::from(report_file)),
            //     None => None
            // };
            let port = match port {
                Some(port) => port,
                None => 1521,
            };
            

            let page_size = match page_size {
                Some(page_size) => page_size,
                None => oracle::table::PAGE_SIZE,
            };

            log::debug!("Build-in Data Type: {:?}", mysql::table::TYPE_SUPPORT);

            // Default
            let mut new_data_type_support = HashMap::new();
            for typ in mysql::table::TYPE_SUPPORT {
                new_data_type_support.insert(typ.to_string(), 1);
            }

            // 完全支持
            if let Some(data_type_support) = data_type_support {
                for typ in data_type_support {
                    if !new_data_type_support.contains_key(typ.as_str()) {
                        new_data_type_support.insert(typ.to_string(), 1);
                    }
                }
            }
            // 不支持
            if let Some(data_type_unsupport) = data_type_unsupport {
                for typ in data_type_unsupport {
                    if new_data_type_support.contains_key(typ.as_str()) {
                        new_data_type_support.remove(typ.as_str());
                    }
                }
            }
            // 有限支持
            if let Some(data_type_support_limited) = data_type_support_limited.clone() {
                for typ in data_type_support_limited {
                    if new_data_type_support.contains_key(typ.as_str()) {
                        new_data_type_support.remove(typ.as_str());
                    }
                }
            }

            let new_data_type_support: Vec<String> = new_data_type_support.into_keys().collect();

            

            let opts = match db_name {
                Some(db_name) => Opts::from_url(&format!(
                    "mysql://{}:{}@{}:{}/{}?pool_min=1&pool_max=10",
                    user, password, host, port, db_name
                ))?,
                None => Opts::from_url(&format!(
                    "mysql://{}:{}@{}:{}/?pool_min=1&pool_max=10",
                    user, password, host, port
                ))?
            };
            let pool = Pool::new(opts)?;
            log::debug!("pool: {:?}", pool);
            let sqlite_conn = sqlite::open(build_in_db).unwrap();
            mysql::probe(
                &sqlite_conn,
                &pool,
                &manifest_file,
                &new_data_type_support,
                &data_type_support_limited,
                page_size,
                &report_file,
                &user,
                &grant_manifest_file,
                show_nullable_uk.unwrap(),
            )
            .await;
        }
        _ => {}
    }

    println!("Bye.");
    Ok(())
}
